/**
 * 虚拟DOM
 *  <div id="virtual-dom">
 *    <p>Virtual DOM</p>
 *    <ul id="list">
 *      <li class="item">Item 1</li>
 *      <li class="item">Item 2</li>
 *      <li class="item">Item 2</li>
 *      <li class="item">Item 3</li>
 *    </ul>
 *  <div>Hello World</div>
 * </div>
 */
// 1.使用js 对象模拟DOM树
function Element (tagName, props, children) {
  this.tagName = tagName
  this.props = props
  this.children = children
  // dom元素的key值 用作唯一标识符
  if (props.key) {
    this.key = props.key
  }
  var count = 0
  children.forEach(function (child, i) {
    if (child instanceof Element) {
      count += child.count
    } else {
      children[i] = '' + child
    }
    count++
  })
  this.count = count
}
function createElement (tagName, props, children) {
  return new Element(tagName, props, children)
}
var ul = createElement('div', { id: 'virtual-dom' }, [
  createElement('p', {}, ['Virtual DOM']),
  createElement('ul', { id: 'list' }, [
    createElement('li', { class: 'item' }, ['Item 1']),
    createElement('li', { class: 'item' }, ['Item 2']),
    createElement('li', { class: 'item' }, ['Item 3'])
  ]),
  createElement('div', {}, ['Hello World'])
])
// 渲染用JS表示的DOM对象
Element.prototype.render = function () {
  // 根据tagName 构建一个真正的DOM节点 然后设置这个节点属性  最后递归把自己的子节点也构建起来
  var el = document.createElement(this.tagName)
  var props = this.props
  // 设置节点的DOM属性
  for (const propName in props) {
    var propValue = props[propName]
    // 设置html属性 arg: 属性名和属性值
    el.setAttribute(propName, propName)
  }
  var children = this.children || []
  children.forEach(function (child) {
    var childEl = (child instanceof Element) ?
      child.render() // 如果子节点也是虚拟DOM 则递归构建DOM节点
      : document.createTextNode(child) // 如果是字符串,只构建文本节点
    el.appendChild(childEl)
  })
  return el
}
// diff函数 对比两棵树
function diff (oldTree, newTree) {
  var index = 0 // 当前节点的标志
  var patches = {} // 用来记录每个节点差异的对象
  dfsWalk(oldTree, newTree, index, patches)
  return patches
}
// 比较两颗虚拟DOM树的差异 -diff算法
// (1) 深度优先遍历 
function dfsWalk (oldNode, newNode, index, patches) {
  var currentPatch = []
  if (typeof (oldNode) === 'string' && typeof (newNode) === 'string') {
    // 文本内容发生改变
    if (newNode !== oldNode) {
      currentPatch.push({ type: patches.TEXT, content: newNode })
    }
  } else if (newNode != null && oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {
    // 节点相同 比较属性
    var propsPatches = diffProps(oldNode, newNode)
    if (propsPatches) {
      currentPatch.push({ type: patches.PROPS, props: propsPatches })
    }
    // 比较子节点 如果子节点又ignore 属性则不需要比较
    if (!isIgnoreChildren(newNode)) {
      diffChildren(oldNode.children, newNode.children, index, patches, currentPatch)
    } else if (newNode !== null) {
      // 新节点和旧节点不同用 replace 替换
      currentPatch.push({ type: patch.REPLACE, node: newNode })
    }
    if (currentPatch.length) {
      patches[index] = currentPatch
    }
    // patches[1]: p, patches[3]:ul
  }
}

// 2 差异类型
// DOM 操作导致的差异类型包括
/**
 * 1: 节点替换:节点改变了 例如将div 换成h1
 * 2: 顺序互换: 移动、删除、新增子节点 例如把上面div的子节点把p和ul顺序呼唤
 * 3: 属性更改: 修改了节点的属性例如把上面li的class 样式类删除
 * 4: 文本改变: 改变文本节点文本内容
 */
// 定义差异类型
var REPLACE = 0 // 替换原先的节点
var REORER = 1 // 重新排序
var PROPS = 2 // 改变了节点的属性
var TEXT = 3  // 文本内容改变
var ul1 = createElement('div', { id: 'virtual-dom' }, [
  createElement('p', {}, ['Virtual DOM']),
  createElement('ul', { id: 'list' }, [
    createElement('li', { class: 'item' }, ['Item 1']),
    createElement('li', { class: 'item' }, ['Item 2']),
    createElement('li', { class: 'item' }, ['Item 3'])
  ]),
  createElement('div', {}, ['Hello World'])
])
var ul2 = createElement('div', { id: 'virtual-dom' }, [
  createElement('p', {}, ['Virtual DOM']),
  createElement('ul', { id: 'list' }, [
    createElement('li', { class: 'item' }, ['Item 21']),
    createElement('li', { class: 'item' }, ['Item 23'])
  ]),
  createElement('p', {}, ['Hello World'])
])
var patches = diff(ul1, ul2)
console.log(patches);